Messaging Patterns in ao
This reference guide explains the messaging patterns available in ao and when to use each one.
Quick Reference: Choosing the Right Pattern
| If you need to... | Process Flow | Key function(s) |
|---|---|---|
| Send a message without waiting for a response | A → B | ao.send |
| Send a message and wait for a response | A → B → A | ao.send().receive() |
| Process messages and respond to the sender | B → A | Handlers.add + msg.reply |
| Create a chain of processing services | A → B → C → A | msg.forward + ao.send().receive() |
| Wait for any matching message regardless of sender | Any → A | Receive (capital R) |
| Create a standard automated response | B → A | Handlers.utils.reply |
Sending Messages
ao.send: Asynchronous Message Sending
Non-blocking direct A → B messaging that returns immediately after sending.
- Use for fire-and-forget notifications or starting async conversations
- Returns a promise-like object that can be chained with
.receive()if needed - Good for parallel processing since it doesn't block execution
Client (A) → Service (B)
↓ ↓
Continues Processes
execution messageBasic Send Example:
-- Non-blocking send example
local serviceId = "process-789" -- Process ID of the target service
ao.send({
Target = serviceId,
Data = "Hello!",
Action = "Greeting"
})
-- Code here runs immediately after sendingmsg.reply: Asynchronous Response Sending
Non-blocking B → A response with automatic reference tracking. Used within handlers to respond to incoming messages.
- Automatically links response to original message via
X-Reference - Enables asynchronous request-response patterns
- Automatically sets
Targetto the original sender orReply-Toaddress if specified
Client (A) → Service (B)
←
Response tagged with X-ReferenceHandler Reply Example:
-- Non-blocking reply in a handler
Handlers.add("greeting-handler",
{ Action = "Greeting" },
function(msg)
msg.reply({ Data = "Hi back!" }) -- Returns immediately
-- Handler continues executing here
end
)msg.forward: Message Forwarding
Non-blocking multi-process routing for A → B → C → A patterns. Creates a sanitized copy of the original message.
- Takes a
targetand a partial message to overwrite forwarded message fields - Preserves
Reply-ToandX-Referenceproperties for complete message tracking - Sets
X-Originto original sender, enabling final service to reply directly to originator
Client (A) → Service (B) → Backend (C)
↖ ↙
Response with X-ReferenceMulti-Process Pipeline Example:
-- In client process
local middlewareProcessId = "process-123"
local finalProcessId = "process-456"
-- Send to middleware and wait for response from final service
local response = ao.send({
Target = middlewareProcessId,
Action = "Transform",
Data = "raw-data"
}).receive(finalProcessId) -- Explicitly wait for response from final service
-- In middleware service
Handlers.add("transform-middleware",
{ Action = "Transform" },
function(msg)
local finalProcessId = "process-456"
msg.forward(finalProcessId, {
Data = msg.Data .. " (pre-processed)",
Action = "Transform-Processed"
})
end
)
-- In final service
Handlers.add("final-processor",
{ Action = "Transform-Processed" },
function(msg)
-- No need to know the client ID - it's stored in X-Origin
msg.forward(msg['X-Origin'], {
Data = msg.Data .. " (final processing complete)",
Action = "Transform-Complete"
})
end
)Handlers.utils.reply: Simple Reply Handler Creation
Creates a handler function that automatically replies with a fixed response. A wrapper around msg.reply for common use cases.
Simple String Response Example:
-- Simple string response handler
Handlers.add("echo-handler",
{ Action = "Echo" },
Handlers.utils.reply("Echo reply!")
)
-- Equivalent to:
Handlers.add("echo-handler",
{ Action = "Echo" },
function(msg)
msg.reply({ Data = "Echo reply!" })
end
)Message Table Response Example:
-- Message table response handler
Handlers.add("status-handler",
{ Action = "Status" },
Handlers.utils.reply({
Data = "OK",
Action = "Status-Response"
})
)Receiving Messages
Receive (Capital R): Blocking Pattern Matcher
Blocks execution until any matching message arrives from any sender. Under the hood, this is implemented using Handlers.once, making it a one-time pattern matcher that automatically removes itself after execution.
- Waits for any message matching the pattern, regardless of origin
- Use for synchronous message processing flows or event listening
- Automatically removes the handler after first match (using
Handlers.onceinternally)
Process (A)
↓
Blocks until match received
↓
Continues executionMessage Pattern Matching Example:
-- Blocks until matching message received
local msg = Receive({
Action = "Update"
})
if msg then
-- Process message
endao.send().receive (Lowercase r): Blocking Reference Matcher
Blocks execution until a specific reply arrives, enabling A → B → A and A → B → C → A request-response cycles.
- Only matches messages linked by
X-Reference - Can specify a target process ID to indicate which process will reply
- Implicitly waits for the proper response based on message reference chains
- For A → B → A flows, process B uses
msg.reply - For A → B → C → A flows, processes B and C use
msg.forward
Basic Request-Response Example:
-- Basic usage: wait for reply from target
local serviceId = "process-789"
local reply = ao.send({
Target = serviceId,
Action = "Query",
Data = { query: "select" }
}).receive() -- Blocks until response receivedMessage Properties
The following properties track message chains and ensure proper routing:
Reference: Unique identifier automatically assigned to each message.Reply-To: Specifies the destination for responses.X-: Any property starting withX-denotes a 'forwarded' tag and is automatically managed by the system.X-Reference: Maintains the conversation chain across replies and forwards.X-Origin: Tracks the conversation originator.
The system automatically manages these properties when using msg.reply and msg.forward. Check out the source code to see exactly how these properties are managed.
Blocking vs. Non-Blocking
Functions either pause your code or let it continue running:
- Non-blocking (
ao.send,msg.reply,msg.forward): Send and continue execution - Blocking (
Receive,.receive()): Pause until response arrives